import os
import random
import re
import time
import traceback
import pandas as pd
import requests
from lxml import etree

# 创建项目文件夹、存储路径
file_path = r'D:\【1】_8G副业冲浪\5_闲鱼闲置\x_接单\2025\1_天眼查\2025\1_08 28 被投资公司股东信息'
file_name = '被投资企业名单'
os.makedirs(file_path, exist_ok=True)  # 确保存储目录存在,存在则跳过

# 读取存储待采集企业名录信息的Excel文件
df_originExcel = pd.read_excel(os.path.join(file_path, f'{file_name}.xlsx'))
companies = df_originExcel['企业名称']

# 本次程序执行时，需采集的企业总数量，应对：代码少量测试、全部采集被封IP、程序报错从报错位置之后再采集
companies_current = companies[14:15]
total = len(companies_current)


# 定义函数1：保存为Excel文件
def save_currentDatas(datas, Excelfile, type):
    try:
        # （1）、先自定义[企业详情_指标项]排列顺序
        companyColumns_orded = [
            '序号',
            '原始企业名称',
            '企业名称',
            '差异与否',
            '曾用名',
            # '英文名称',
            '企业详情页链接',
            '注册地址',
            '企业标签',
            '成立日期',
            '行业',
            '国标行业',
            '企业规模',
            # '营业收入',
            '注册资本',
            '实缴资本',
            '员工人数',
            '人员规模',
            '参保人数',
            '分支机构参保人数',
            '法定代表人',
            # '关联企业',
            # '登记状态',
            '天眼评分',
            # '企业类型',
            # '登记机关',
            # '统一社会信用代码',
            # '组织机构代码',
            # '工商注册号',
            # '纳税人识别号',
            # '纳税人资质',
            # '营业期限',
            # '核准日期',
            # '经营范围',
            '数据来源'
        ]
        # 将[企业信息]或[股东信息]数据转换为DataFrame
        df = pd.DataFrame(datas)

        if type == 1:
            # 将[企业详情]数据转换为DataFrame
            # 确保[采集结果(df_company)]中，含有[自定义排序的列(companyColumns_orded)]
            for col in companyColumns_orded:
                if col not in df.columns:  # df.colums 内置方法
                    df[col] = '暂无'  # 处理缺失值
            # print(df_company.columns)
            # 按自定义顺序，重新排列[采集结果]中的列顺序
            df = df[companyColumns_orded]
            # print(df_company)

        # （2）保存为文件时，“新、旧数据”的处理逻辑
        # 如果文件存在，处理：“新采集数据与已保存数据”的合并；否则，直接对新采集数据进行存储为Excel文件处理
        if os.path.exists(Excelfile):
            exist_df = pd.read_excel(Excelfile)  # 读取“已保存”的Excel文件
            # 文件拼接合并pd.concat：将“已保存在Excelfile的数据”与“新采集的数据（i%10）”进行拼接，合并生成1个df数据。ignore_index=True：合并后重新生成“行索引”，避免索引重复
            df = pd.concat([exist_df, df], ignore_index=True)

        df.to_excel(Excelfile, index=False)


    except Exception as e:
        print(f'紧急保存失败：{str(e)}')


# 定义存储所有企业数据的变量
all_Datas = []

# 存储股东信息的列表
shareholders = []

# 定义临时保存文件
companyTemp_file = f'{file_path}\[企业详情]结果_临时保存.xlsx'
holderTemp_file = f'{file_path}\[股东信息]结果_临时保存.xlsx'

# 使用try-except进行异常捕获，保证任何程序中途报错/崩溃等，都能执行：最后的数据保存工作。避免没保存已采集的数据，还要浪费cookie重新采集！！
try:
    # 依次取出companies列表中的每个[企业名称]，同时记录它是第几个（从1开始计数，对idx赋值为1），分别用idx 和 company接收这两个值
    for idx, company in enumerate(companies_current, 1):  # for idx, company in companies[:1].items():
        # --------------------------------------
        # 一、构建带有[企业名称]的天眼查搜索网址
        # --------------------------------------
        base_url = 'https://www.tianyancha.com/search?'
        params = {
            'key': company
        }
        headers = {
            'referer': 'https://www.tianyancha.com/',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
            'accept-encoding': 'gzip, deflate, br, zstd',
            'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
            'connection': 'keep-alive',
            'host': 'www.tianyancha.com'
        }

        # 无法偷懒放到header参数内，放到header里是str文本格式，服务器识别不到cookie登录信息，只能停留在登录页面；只有单独放到cookies参数里，转换为字典格式才行
        cookies = {
            "CUID": "0d926961090b28582b8f672c68156f68",
            "TYCID": "83743710023111f08ba0595a061800c3",
            "ssuid": "652323178",
            "_ga": "GA1.2.188090159.1742110665",
            "sensorsdata2015jssdkcross": "%7B%22distinct_id%22%3A%2215206584%22%2C%22first_id%22%3A%221959dae8224598-011f2efc6b090f6-4c657b58-2304000-1959dae82251a87%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTk1OWRhZTgyMjQ1OTgtMDExZjJlZmM2YjA5MGY2LTRjNjU3YjU4LTIzMDQwMDAtMTk1OWRhZTgyMjUxYTg3IiwiJGlkZW50aXR5X2xvZ2luX2lkIjoiMTUyMDY1ODQifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%2215206584%22%7D%2C%22%24device_id%22%3A%221959dae8224598-011f2efc6b090f6-4c657b58-2304000-1959dae82251a87%22%7D",
            "bannerFlag": "true",
            "HWWAFSESTIME": "1756520795724",
            "HWWAFSESID": "c930ee8f7991054351",
            "csrfToken": "JdlFvgBKrUVMvx7OtVFMKsmG",
            "Hm_lvt_e92c8d65d92d534b0fc290df538b4758": "1756520591,1756520801",
            "HMACCOUNT": "C36DD46FEA3657F3",
            "tyc-user-info": "%7B%22state%22%3A%220%22%2C%22vipManager%22%3A%220%22%2C%22mobile%22%3A%2218669769250%22%2C%22userId%22%3A%2215206584%22%7D",
            "tyc-user-info-save-time": "1756520888400",
            "auth_token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxODY2OTc2OTI1MCIsImlhdCI6MTc1NjUyMDg4OCwiZXhwIjoxNzU5MTEyODg4fQ.0HirTYqknPZL_TeOzWcxB8TXi3JOlxhOPDRYR5GRKAgp2W8vtFBzNqLWMCiDnS-7py8ayxDQPieXFCPPkgbfOQ",
            "Hm_lpvt_e92c8d65d92d534b0fc290df538b4758": "1756541608"
        }
        response = requests.get(url=base_url, params=params, headers=headers, cookies=cookies, timeout=10)
        response.encoding = 'utf-8'
        html = etree.HTML(response.text)

        # ----------------------------------------------------------------------------------------------
        # 二、获取待采集企业所在的[企业详情网址]：获取_搜索到的带有[企业名称]的链接列表中的[第1项链接]，并转换为str格式
        # ----------------------------------------------------------------------------------------------
        # company_url_list=html.xpath('//*[@id="page-container"]/div/div[2]/div/div[2]/div[2]/div[1]/div/div[3]/div[2]/div[1]/div/a/@href')
        # 尽量用模糊查询，有的网页因为部分企业部分指标项缺失，会导致div、class等绝对值发生变化
        # 将[搜索结果列表中的“第1项”元素]作为目标查询企业
        company_url = html.xpath(
            '//*[@id="page-container"]//div[@class="index_list-content__wjkNi"]/div[1]//a[@class="index_alink__zcia5 link-click"]/@href')[
            0]
        # company_url=company_url_list[0]

        # 提取企业id，用于后续提取完整的[股东信息]详情
        company_id = company_url.split('/')[-1]
        print(f'🔍 开始搜索: 第[{idx}/{total}]家企业：{company}, {company_url}')
        # 每搜索1次企业，一大sleep
        time.sleep(random.uniform(1.5, 4))

        # --------------------------------------------------------------------------------------------------------
        # 三、访问、并打开[企业详情页]。！！注意：访问详情页时，也需要填入headers、cookies等参数，否则会被识别出爬虫、被拦截！！
        # --------------------------------------------------------------------------------------------------------
        response = requests.get(company_url, headers=headers, cookies=cookies, timeout=10)
        response.encoding = 'utf-8'
        company_html = etree.HTML(response.text)
        # print(company_html)

        # --------------------------------------------------
        # 四、采集数据：依需在[企业详情页]中采集[企业相关数据]
        # --------------------------------------------------
        # 创建存储{企业指标项：指标值}的字典，并初始化内容值
        company_data = {
            '序号': idx,
            '原始企业名称': company,
            '数据来源': '公众号：十点柠檬'
        }

        # ======== 4.1、[企业标签]。 ========
        company_tags = company_html.xpath(
            '//*[@id="J_CompanyHeaderContent"]//div[@class="index_tag-list-content__E8sLp"]//text()')
        company_tags = ','.join([tag.strip() for tag in company_tags if tag.strip()]).replace('曾用名,', '').replace(
            ',合作风险', '').replace(',司法案件', '')
        company_data['企业标签'] = company_tags

        # ======== 4.2、[企业详情第3栏]数据。 ========
        # 包括：（1）国标行业——国民经济行业：中类；（2）企业规模；（3）员工人数；（4）营业收入
        # 全部在<div class="detail-item index_detail-item-third__ukvtr">标签下  #third

        # 在网页源代码中定位：存储[所有企业详情指标]的“容器”位置。
        companyInfo_items = company_html.xpath(
            '//*[@id="J_CompanyHeaderContent"]/div[2]/div[2]/div[@class="detail-item index_detail-item-third__ukvtr"]')
        # print(companyInfo_items)   # [<Element div at 0x218f0c17ec0>],len(companyInfos_items)==1,所有的内容全部在1个列表中了
        # print(type(companyInfo_items))  # <class 'list'>

        # 由于xpath（）方法只针对Element元素树，上一步生成的结果，类型为list，且仅有1个元素，直接用[0]提取出来就行。无需再用for循环遍历了
        companyInfo_Element = companyInfo_items[0]
        # print(type(companyInfos_Element))  #<class 'lxml.etree._Element'>

        # 分析网页结构后，[国标行业]、[企业规模]、[员工人数]和[营业收入]4项指标项与具体值，
        # 全部在<div class包含"index_detail-info-item__oAOqL">标签下，除[企业规模]是模糊标签外，[国标行业]、[员工人数]为绝对值匹配
        # 但是[营业收入]与前3项的网页结构不同：
        # 前3项全部在2级（子节点）//<span class="index_detail-label__oRf2J">标签内的1级文字标签/text()
        # [营业收入]：减少了一层<span class="index_inline-flex__QLDiW">标签，div标签下的一级标签（父节点）便是<span class="index_detail-label__oRf2J">了

        # 为了逐一提取所有{企业指标项：指标值}元素，方便for循环内遍历处理（拼接、替换、删除）每个指标项。先对上一级span标签进行提取，返回一个列表
        # 提取每一级<div class包含"index_detail-info-item__oAOqL">标签下的所有内容item{企业指标项：指标值}
        items_Element = companyInfo_Element.xpath('./div[contains(@class,"index_detail-info-item__oAOqL")]')
        # print(items_Element)
        # print(len(items_Element))   # 4

        # 遍历items_Element中的所有子元素item{企业指标项：指标值}
        for i, item in enumerate(items_Element, 1):
            # print(i,'----',item)

            # ======== 4.2.1、逐一提取[企业详情]的[指标项：label]。包括：国标行业、企业规模、员工人数和营业收入4项。 ========
            # 全部在<span class包含"index_detail-label__oRf2J">标签下，既有/<span>,也有//<span>   #label
            label = item.xpath('.//span[@class="index_detail-label__oRf2J"]//text()')
            # print(label)

            # 由于[营业收入]指标项的“冒号：”和“营业收入是分开的”，导致会得出5个指标项（①'国标行业：'、②'企业规模：'、③'员工人数：'、④'营业收入'、⑤'：'）
            # 可以先将各个[指标项]中分离的“要素”拼接位一个完整的字段；然后再清洗数据：去除首位空格、回车，替换冒号等
            label = ''.join([part.strip() for part in label]).replace('：', '')  # 注意：冒号的全、半角
            # print(label)

            # ======== 4.2.2、逐一提取[企业详情]的[指标值：value]。 全部在<span class包含"index_detail-text__Ac9Py">标签下   #text ========
            value = item.xpath('.//span[contains(@class,"index_detail-text__Ac9Py")]//text()')
            value = ''.join([part.strip() for part in value]).replace('(', '').replace(')', '')
            # print(value)

            company_data[label] = value

            # 每采集1次[企业详情第3栏]中的每一小行数据，一小sleep
            time.sleep(random.uniform(0.4, 0.9))

        # ======== 4.3、[工商信息]表格。 ========
        # 定位：存储[所有工商信息]表格“容器”的位置,并获取所有[行]。————任意一级class="index_tableBox__ZadJW "的标签下的/<tbody>标签下/的所有行<tr>
        rows = company_html.xpath('//*[@class="index_tableBox__ZadJW "]/tbody/tr')  # 与前面的Line100-107:企业详情第3栏原理相同
        # print(rows)   # [<Element tr at 0x2723ba37fc0>, <Element tr at 0x27239c27840>, …………]
        # print(type(rows))     # <class 'list'>
        # print(type(rows[0]))  # <class 'lxml.etree._Element'>
        # print(len(rows))      # 11行

        # ======== 4.3.1、遍历[所有行]，提取[每一行]中的[所有单元格内容]。 ========
        # 由于每一行[第1列]、间隔一列（第1、3、5列）是[指标项]，第2、4、6是[指标值]
        for row in rows:
            # 获取当前行的[所有列]的单元格值
            columns = row.xpath('.//td')
            # print(columns)   # list [<class 'lxml.etree._Element'>,<class 'lxml.etree._Element'>…………]
            # print(len(columns))   # 2,6,2,6,6,6……………

            # ======== 4.3.2、隔列、依次，提取{指标项：指标值} ========
            i = 0
            # 只要num小于[当前行]中的[列]的数量
            while i < len(columns):
                # （1）提取有可能是[指标项_列]的[某列/当前列]的单元格值。
                # 一般，表格中列的结构为：[列：指标项——列：指标值]、[列：指标项——列：指标值]…………所以第1列(i=0)、第3、5列(i+2)必然是[指标项]，第2、4列（i+1）……通常是[指标值]
                label_cells = columns[i].xpath('.//text()')
                # print(label_cells)
                # 先去除首尾空格、回车等空字符串，再将[一列]中包含多个文本值的内容拼接为1个字符串作为[列值]，再去除“非换行空格”。
                label = ''.join([cell.strip() for cell in label_cells]).replace('\xa0', '')
                # print(label)

                # 如果label为空值，或者只包含：英文冒号、中文冒号、空白字符等，则i+=1、处理下一列单元格，并跳过当前循环的剩余内容
                if not label or re.match(r'^[:：\s]*$', label):
                    i += 1
                    continue  # 跳过当前循环的剩余部分，直接进入下一次循环

                # （2）提取[指标项_列]对应的[指标值_列]的单元格值
                if i + 1 < len(columns):
                    # 去掉[法定代表人]对应的[指标值_列]中单独被摘出来的“姓氏”
                    if label == '法定代表人':
                        value_cells = columns[i + 1].xpath('.//div[@class="_8b1c5"]/a/text()')
                    else:
                        value_cells = columns[i + 1].xpath('.//text()')

                    value = ''.join([cell.strip() for cell in value_cells]).replace('\xa0', '')
                    # print(value)

                company_data[label] = value

                # 增加企业详情页，防止某个企业数据采集错误，方便定位与查询
                company_data['企业详情页链接'] = company_url

                # ======== 4.3.3、部分字段拆分、清洗处理 ========
                if label == '企业名称':
                    # 检查是否含有[曾用名]
                    if '曾用名' in value:
                        value = value.split('曾用名')
                        company_data['企业名称'] = value[0]
                        company_data['曾用名'] = value[1].replace('更多1', '').replace('更多2', '')
                    else:
                        company_data['企业名称'] = value
                        company_data['曾用名'] = ''

                    # 对比原始表格与天眼查搜索的[企业名称]是否一致，是否为想要查找的企业
                    if company_data['企业名称'] != company:  # 对比校核天眼查中的“企业名称”是否与原始表格中的“原始企业名称”有差异
                        company_data['差异与否'] = '需更新校核：原始企业名称'

                if label == '法定代表人':
                    # 检查是否含有[关联企业]
                    if '关联企业' in value:
                        value = value.split('关联企业')
                        company_data['法定代表人'] = value[0]
                        company_data['关联企业'] = int(value[1])
                    else:
                        company_data['法定代表人'] = value
                        company_data['关联企业'] = ''

                if label == '注册地址':
                    value = value.replace('附近公司', '')
                    company_data['注册地址'] = value

                if label == '英文名称':
                    value = value.replace('（自动翻译）', '')
                    company_data['英文名称'] = value

                i += 2  # 隔列进入下一组{指标项：指标值}的循环，处理下2列

            # 每采集1行[工商信息表格]中的一行数据，一小sleep
            time.sleep(random.uniform(0.4, 0.9))

        # # ======== 4.4、[主要成员]表格。 ========
        # # 构造请求URL和参数
        # registration_url = 'https://capi.tianyancha.com/cloud-company-background/company/dim/staff/registration?'
        # params = {
        #     # 时间戳
        #     '_': int(time.time() * 1000),
        #     # 最重要的参数：企业id，与company_url末尾的号码相同
        #     'gid': company_id,
        #     'pageSize': 20,
        #     'pageNum': 1,
        #     'stockType': 1,
        # }
        #
        # # 发送请求
        # response = requests.get (registration_url, params=params, timeout=10)
        # colgroup_datas_all=response.json()

        # ======== 4.5、[股东信息]表格。 ========
        # 构造请求URL和参数
        announcement_url = 'https://capi.tianyancha.com/cloud-company-background/companyV2/dim/holder/latest/announcement?'

        payload = {
            # 时间戳
            '_': int(time.time() * 1000),
            'benefitSharesType': 1,
            # 最重要的参数：企业id，与company_url末尾的号码相同
            'gid': company_id,
            'historyType': 'null',
            'keyword': "",
            'pageNum': 1,
            'pageSize': 50,
            'percentLevel': "-100",
            'subscribedCapital': "-100",
            '_unUseParam': 0
        }

        # post!发送请求
        response = requests.post(announcement_url, json=payload, timeout=10)
        holder_datas_all = response.json()
        # print(type(holderdatas_all))   # <class 'dict'>
        # print(holderdatas_all)
        holder_results_all = holder_datas_all['data']['result']


        # print(type(holder_results_all))   # <class 'list'>
        # print(holder_results_all)

        # 定义子函数1：空值判断。判断获取的值是否为空值
        def null_judge(value, default='-'):
            # 如果为空值或空，返回‘-’；否则返回value值本身
            return default if value is None or value == '' else value


        # 定义子函数2：格式转换。对数字分别转换为浮点型
        def format_float(value, default='-'):
            if value is None or value == '' or value == '-':
                return default
            try:
                # 如果value为str格式，且包含%
                # 先判断是否含有%
                if isinstance(value, str) and '%' in value:
                    value = value.replace('%', '')

                # 不管是否为“已替换%的str”或其他未执行if的value（如：数字或不含%的str），都需要执行return
                # 如果遇到无法执行float的数字或其他符号，直接进入except
                return float(value)
            except(ValueError, TypeError):
                return default


        for i, result in enumerate(holder_results_all, 1):
            # 如果 result 中没有 [实缴出资额（万元）:totalActualCapital4PC] 等字段只写填“暂无”
            shareholder = {
                '被投资企业': company,
                '股东序号': i,
                '股东名称': null_judge(result.get('shareHolderName')),
                '持股比例（%）': format_float(result.get('percent')),
                '认缴出资额（万元）': format_float(result.get('totalCapital4PC')),
                '认缴出资日期': null_judge(result.get('subscribedDate')),
                # '认缴出资日期':result.get('capital')[0].get('time','-'),  #空值，直接赋值为“-”  #subscribedDate

                '实缴出资额（万元）': format_float(result.get('totalActualCapital4PC')),
                '实缴出资日期': null_judge(result.get('actualDate')),
                '间接持股比例（%）': format_float(result.get('indirectBenefitShares')),
                '首次持股日期': null_judge(result.get('investmentStartTime')),
                '关联产品/机构': null_judge(result.get('productAndJigou')),
                '数据来源': '公众号：十点柠檬'
            }
            # print(shareholder)
            shareholders.append(shareholder)
            # print(shareholders)

        # # ======== 4.6、[投资机构信息]表格。 ========
        # # 构造请求URL和参数
        # invest_url = 'https://capi.tianyancha.com/cloud-company-background/company/investListV2?'
        #
        # payload = {
        #     # 时间戳
        #     '_': int(time.time() * 1000),
        #     'benefitSharesType': 1,
        #     # 最重要的参数：企业id，与company_url末尾的号码相同
        #     'gid': company_id,
        #     'pageNum': 1,
        #     'pageSize': 10
        # }
        #
        # # post!发送请求
        # response = requests.post(invest_url, json=payload, timeout=10)
        # holder_datas_all = response.json()

        # ========================================！！目前，还未测试成功！！=====================================================
        # ！！！遇到了懒加载？JavaScript动态加载？？？不会处理！！！！
        # 定位：存储[股东信息]表格“容器”的位置,并获取所有[行]。 '//*[@class="table-wrap expand-table-wrap"]/thead'
        # table_shareholders = company_html.xpath('//*[@class="table-wrap expand-table-wrap"]/thead')[0]
        # print(table_shareholders)   # [<Element thead at 0x13e32c3c6c0>]
        # # print(type(table_shareholders))     # <class 'list'>
        # # print(type(table_shareholders[0]))  # <class 'lxml.etree._Element'>
        # # print(len(table_shareholders))      # 1行
        #
        # # ======== 4.4.1、获取[股东信息]表格的[表头]要素。 ========
        # table_theads=table_shareholders.xpath('.//tr/th/span//text()')
        # # print(table_theads)
        # # '//*[@id="JS_Layout_Dims"]/div[2]/div[2]/div[4]/div/div[2]/div[2]/div/table/thead/tr/th/span/text()'
        #
        # # 再获取表体内容
        # table_tbody = company_html.xpath('//*[@class="table-wrap expand-table-wrap"]/tbody')[0]
        # table_tbody_data = table_tbody.xpath('.//tr/td//text()')
        #
        # # 这里可以根据表头和表体数据进行整理，比如按行组合等操作
        # # 简单打印表头
        # print(table_theads)
        # # 简单打印表体数据
        # print(table_tbody_data)
        # ========================================！！目前，还未测试成功！！=====================================================

        print(company_data)
        # print(shareholders)

        rest_num = total - idx
        print(f'✅ 成功采集到：[{idx}]家企业相关数据。\n'
              f'     💛 还剩：[{rest_num}]家企业\n')
        all_Datas.append(company_data)

        # 最后再一小小sleep 1秒，进入下一轮的企业搜索、详情数据采集工作
        time.sleep(random.uniform(0.5, 0.8))

        # ----------------------------------------------------------
        # 五、保存数据：阶段性临时保存，以防中间报错等无法存储；+最后结果保存
        # ----------------------------------------------------------

        # 定时保存：每采集5家企业，临时保存一次Excel文件
        if idx % 5 == 0:
            save_currentDatas(all_Datas, companyTemp_file, 1)
            save_currentDatas(shareholders, holderTemp_file, 2)
            print(f'🥂 已临时保存[{idx}]家企业数据')

    # 全部采集完成后，保存所有数据，避免因数量“不足5家”而不能阶段保存的数据丢失。
    timestamp = time.strftime("%Y%m%d_%H%M%S")
    companyFinal_file = os.path.join(file_path, f'{file_name}_[企业详情]最终结果_{timestamp}.xlsx')
    holderFinal_file = os.path.join(file_path, f'{file_name}_[股东信息]最终结果_{timestamp}.xlsx')

    save_currentDatas(all_Datas, companyFinal_file, 1)

    # ====== 股东信息（1V多），单独保存到一个新的Excel文件中。与企业的其他详情信息（1V1）并没有放在一起
    save_currentDatas(shareholders, holderFinal_file, 2)

    print(f'\n🤗 恭喜！'
          f'\n🌹 所有[{total}]家企业的：[企业详情]已全部采集完成，并保存为：{companyFinal_file}'
          f'\n🌹 [{len(shareholders)}]条[股东信息]已全部采集完成，并保存为：{holderFinal_file}')

    # 删除[临时保存]文件
    tempflies = [companyTemp_file, holderTemp_file]
    for file in tempflies:
        if os.path.exists(file):
            os.remove(file)
    print(f'⌛ 同时，已为您删除临时文件:{tempflies}')


# 捕获所有异常，并在即使程序报错时仍能执行：保存工作
except Exception as e:
    # 捕获错误信息
    print(f'\n{"=" * 20} 程序突发报错 {"=" * 20}\n'
          f'❌ 第[{idx}]家企业："{company}"采集遇到问题\n'
          f'错误类型：{type(e).__name__}\n'
          f'错误字段：{str(e)}\n'
          f'错误详情：')
    traceback.print_exc()
    print(f'\n{"=" * 50}')

    # 生成带错误标记的临时文件
    error_timestamp = time.strftime("%Y%m%d_%H%M%S")
    # 命名定义[报错时_保存报错前数据]的文件名
    companyError_file = os.path.join(file_path, f'[企业详情]结果_报错前保存_{error_timestamp}.xlsx')
    holderError_file = os.path.join(file_path, f'[股东信息]结果_报错前保存_{error_timestamp}.xlsx')

    # 保存：报错前已成功采集到的数据
    save_currentDatas(all_Datas, companyError_file, 1)
    save_currentDatas(shareholders, holderError_file, 2)

    if len(all_Datas) > 0 or len(shareholders) > 0:
        print(f'\n程序突发报错,已分别保存采集到的相关数据：'
              f'\n👉️ [{len(all_Datas)}]家企业的[企业详情]：{companyError_file}'
              f'\n👉️ 及其[{len(shareholders)}]条股东信息：{companyError_file}分别保存。'
              )
